package org.rsbot.injector;

import org.rsbot.injector.adapters.ModelHackAdapter;
import org.rsbot.injector.adapters.ClassHackAdapter;
import org.rsbot.injector.data.MasterSetterData;
import org.rsbot.injector.data.ClassData;
import org.rsbot.injector.data.SetterData;
import org.rsbot.injector.data.GetterData;
import org.rsbot.loader.asm.*;
import org.rsbot.util.Serializer;

import java.io.File;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Logger;

import org.rsbot.injector.adapters.MasterHackAdapter;
import org.rsbot.injector.data.ModelData;

import org.rsbot.loader.asm.ClassReader;
import org.rsbot.util.GlobalConfiguration;
import org.rsbot.util.UpdateUtil;

/**
 * @author Nader Sleiman.
 */
public class Injector implements Opcodes {

    private static Logger log;

    static {
        log = Logger.getLogger(Injector.class.getName());
    }

    public static void injectClasses(Map<String, byte[]> classes) {

        final Vector v = (Vector) Serializer.deserialize(getFile());
        final ClassDataVector<ClassData> classes_data = (ClassDataVector<ClassData>) v.get(0);
        final ModelDataVector<ModelData> model_data_vec = (ModelDataVector<ModelData>) v.get(1);
        final MasterSetterData master_data = (MasterSetterData) v.get(2);

        ClassWriter cw = null;
        ClassAdapter ca = null;
        ClassReader cr = null;
        MethodVisitor mv = null;


//Inject interfaces + superclass + Fields via a sexy adapter xD.
        for (ClassData cd : classes_data) {
            cw = new ClassWriter(0);
            //Inject interfaces + superclass + Fields via a sexy adapter xD.
            ca = new ClassHackAdapter(cw, cd);
            cr = new ClassReader(classes.get(cd.name));
            cr.accept(ca, 0);
            //Inject Getters
            for (GetterData m : cd.getters) {
                mv = cw.visitMethod(m.access, m.name, m.desc, m.signature, m.exceptions);
                mv.visitCode();
                if (!m.isStatic) {
                    mv.visitVarInsn(Opcodes.ALOAD, 0);
                }
                mv.visitFieldInsn((m.isStatic ? Opcodes.GETSTATIC : Opcodes.GETFIELD), m.field_owner, m.field_name, m.field_desc);
                mv.visitInsn(Type.getType(m.field_desc).getOpcode(IRETURN));
                mv.visitMaxs(1, 1);//stack = 1 ,local = 1
                mv.visitEnd();

            }
            for (SetterData sd : cd.setters) {
                mv = cw.visitMethod(sd.access, sd.name, sd.desc, sd.signature, sd.exceptions);
                mv.visitVarInsn(sd.field_desc.equals("I") ? ILOAD : ALOAD, 0);
                mv.visitFieldInsn(PUTSTATIC, cd.name, sd.field_name, sd.field_desc);
                mv.visitInsn(RETURN);
                mv.visitMaxs(1, 1);
            }

            //dump Classes data back to Map
            dump(classes, cd.name, cw);
        }
        //Hack MasterX/Y
        cw = new ClassWriter(0);
        ca = new MasterHackAdapter(cw, master_data);
        cr = new ClassReader(classes.get(master_data.owner));
        cr.accept(ca, 0);

        //dump Master data back to Map
        dump(classes, master_data.owner, cw);
        //Moel Hack
        for (ModelData md : model_data_vec) {

            cw = new ClassWriter(0);
            ca = new ModelHackAdapter(cw, md);
            cr = new ClassReader(classes.get(md.class_name));
            cr.accept(ca, 0);
            dump(classes, md.class_name, cw);

        }
    }

    private static void dump(Map<String, byte[]> classes, String name, ClassWriter cw) {
        //override old value with new one
        classes.put(name, cw.toByteArray());
    }

    private static File getFile() {
        File f = new File(GlobalConfiguration.Paths.Resources.DATA);
        if (f.exists()) {
            return f;
        }
        log.info("couldn't find DataHolder's Cache, downloading from web ..");
        if (!UpdateUtil.download(GlobalConfiguration.Paths.URLs.UPDATE, GlobalConfiguration.Paths.Resources.DATA)) {
            log.info("Failed to get DataHolder");
        }

        return f.exists() ? f : null;
    }
}
